//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the \"Software\"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------

local file = vargv[0]
if (type(file) != "instance" || file.getclass() != File)
{
  throw "expected instance of class File"
}

file.namespace = "sqb"

file.header_includes.append("sqbind/sqbStackHandler.h")
file.header_includes.append("sqbind/sqbStackUtils.h")

const kCallFunctionName = "Call"
const kFunctionParameterName = "function"
const kInstanceTypeName = "InstanceType"
const kInstanceParameterName = "instance"
const kReturnTypeName = "ReturnType"
const kParameterTypeName = "ParameterType"

enum CallTypes
{
  kFunction,
  kClassFunction,
  kConstClassFunction,
}

enum SpecialisationTypes
{
  kNoSpecialisation,
  kVoidSpecialisation
}

//----------------------------------------------------------------------------------------------------------------------
local add_function_arguments = function(call, argument_count)
{
  for (local i = 0; i != argument_count; ++i)
  {
    local comma_text = ((i == (argument_count - 1)) ? "" : ",")
    call.body.add_line(
      "  %s::Get(%s::TypeWrapper<%s%d>(), vm, index + %d)%s",
      file.namespace,
      file.namespace,
      kParameterTypeName,
      i,
      i,
      comma_text)
  }
}

//----------------------------------------------------------------------------------------------------------------------
local add_call_function_body = function(call, argument_count, call_type, specialisation_type)
{
  // get arguments
  for (local i = 0; i != argument_count; ++i)
  {
    call.body.add_line("if (!%s::Match(%s::TypeWrapper<%s%d>(), vm, index + %d))", file.namespace, file.namespace, kParameterTypeName, i, i)
    call.body.add_line("{")
    call.body.add_line("  %s::StackHandler sh(vm);", file.namespace)
    call.body.add_line("  return sh.ThrowParamError(index + %d, %s::TypeInfo<%s%d>().m_typeName);", i, file.namespace, kParameterTypeName, i)
    call.body.add_line("}")
    call.body.add_new_line()
  }

  local pre_call_text = ""
  if (specialisation_type == SpecialisationTypes.kNoSpecialisation)
  {
    pre_call_text = format("%s result = ", kReturnTypeName)
  }

  if (call_type == CallTypes.kClassFunction || call_type == CallTypes.kConstClassFunction)
  {
    if (argument_count == 0)
    {
      call.body.add_line("%s(%s.*%s)();", pre_call_text, kInstanceParameterName, kFunctionParameterName)
    }
    else
    {
      call.body.add_line("%s(%s.*%s)(", pre_call_text, kInstanceParameterName, kFunctionParameterName)
      add_function_arguments(call, argument_count)
      call.body.add_line("  );")
    }
  }
  else
  {
    if (argument_count == 0)
    {
      call.body.add_line("%s%s();", pre_call_text, kFunctionParameterName)
    }
    else
    {
      call.body.add_line("%s%s(", pre_call_text, kFunctionParameterName)
      add_function_arguments(call, argument_count)
      call.body.add_line("  );")
    }
  }

  call.body.add_new_line()

  if (specialisation_type == SpecialisationTypes.kNoSpecialisation)
  {
    call.body.add_line("return %s::Push(vm, result);", file.namespace)
  }
  else
  {
    call.body.add_line("return 0;")
  }
}

//----------------------------------------------------------------------------------------------------------------------
local add_call_function = function(return_specialisation, argument_count, call_type, specialisation_type)
{
  local call = Function(kCallFunctionName, CodeGenerator.kInline | CodeGenerator.kStatic)
  if (specialisation_type == SpecialisationTypes.kVoidSpecialisation)
  {
    call.set_flags(CodeGenerator.kSpecialisation)
    call.specialisation = "void"
  }

  call.return_type = Parameter("SQRESULT")

  local function_pointer = Function(kFunctionParameterName, CodeGenerator.kPointer)
  if (call_type == CallTypes.kClassFunction)
  {
    call.parameters.append(Parameter(kInstanceTypeName, kInstanceParameterName, CodeGenerator.kReference))

    function_pointer.owner = Class(kInstanceTypeName, CodeGenerator.kTemplate)
  }
  else if (call_type == CallTypes.kConstClassFunction)
  {
    call.parameters.append(Parameter(kInstanceTypeName, kInstanceParameterName, CodeGenerator.kReference | CodeGenerator.kConst))

    function_pointer.owner = Class(kInstanceTypeName, CodeGenerator.kTemplate)
    function_pointer.set_flags(CodeGenerator.kConst)
  }

  if (specialisation_type == SpecialisationTypes.kVoidSpecialisation)
  {
    function_pointer.return_type = Parameter("void")
  }
  else
  {
    function_pointer.return_type = Parameter(kReturnTypeName)
  }

  for (local i = 0; i != argument_count; ++i)
  {
    function_pointer.parameters.append(Parameter(format("%s%d", kParameterTypeName, i), "", CodeGenerator.kTemplate))
  }

  call.parameters.append(function_pointer)

  local vm_parameter = Parameter("HSQUIRRELVM", "vm")
  local index_parameter = Parameter("SQInteger", "index")
  if (argument_count == 0)
  {
    if (specialisation_type == SpecialisationTypes.kVoidSpecialisation)
    {
      vm_parameter.set_flags(CodeGenerator.kUnused)
    }
    index_parameter.set_flags(CodeGenerator.kUnused)
  }
  call.parameters.append(vm_parameter)
  call.parameters.append(index_parameter)

  add_call_function_body(call, argument_count, call_type, specialisation_type)

  return_specialisation.public.functions.append(call)
}

//----------------------------------------------------------------------------------------------------------------------
local return_specialisation = Class("ReturnSpecialisation", CodeGenerator.kTemplate)
return_specialisation.template_type = kReturnTypeName

for (local i = 0; i != _OPTIONS["iterations"] + 1; ++i)
{
  // template<typename kReturnTypeName>
  // SQInteger kCallFunctionName(kReturnTypeName (*kFunctionParameterName)(), HSQUIRRELVM vm, SQInteger index)
  //
  add_call_function(return_specialisation, i, CallTypes.kFunction, SpecialisationTypes.kNoSpecialisation)
  add_call_function(return_specialisation, i, CallTypes.kFunction, SpecialisationTypes.kVoidSpecialisation)

  // template<typename kReturnTypeName, typename kInstanceTypeName>
  // SQInteger kCallFunctionName(kInstanceTypeName &kInstanceParameterName, kReturnTypeName (kInstanceParameterName::*kFunctionParameterName)(), HSQUIRRELVM vm, SQInteger index)
  //
  add_call_function(return_specialisation, i, CallTypes.kClassFunction, SpecialisationTypes.kNoSpecialisation)
  add_call_function(return_specialisation, i, CallTypes.kClassFunction, SpecialisationTypes.kVoidSpecialisation)

  // template<typename kReturnTypeName, typename kInstanceTypeName>
  // SQInteger kCallFunctionName(const kInstanceTypeName &kInstanceParameterName, kReturnTypeName (kInstanceParameterName::*kFunctionParameterName)() const, HSQUIRRELVM vm, SQInteger index)
  //
  add_call_function(return_specialisation, i, CallTypes.kConstClassFunction, SpecialisationTypes.kNoSpecialisation)
  add_call_function(return_specialisation, i, CallTypes.kConstClassFunction, SpecialisationTypes.kVoidSpecialisation)
}

file.contents.append(return_specialisation)